package location

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"sort"
	"strings"

	"github.com/duke-git/lancet/v2/cryptor"
	"github.com/duke-git/lancet/v2/maputil"
)

const (
	GPS uint = iota + 1
	Sougou
	Baidu
	Mapbar
	Google
	Sogou_Mercator
)

type Location struct {
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
}

type Address struct {
	Nation   string `json:"nation"`
	Province string `json:"province"`
	City     string `json:"city"`
	District string `json:"district"`
}

type IPLocationResp struct {
	Status  int64  `json:"status"`
	Message string `json:"message"`
	Result  struct {
		Location struct {
			Location
		} `json:"location"`
		Address struct {
			Address
		} `json:"ad_info"`
	} `json:"result"`
}

type option struct {
	key    string
	secret string
}

type locationResp struct {
	Status  int64  `json:"status"`
	Message string `json:"message"`
	Result  struct {
		Location struct {
			Location
		} `json:"location"`
	} `json:"result"`
}

type addressResp struct {
	Status  int64  `json:"status"`
	Message string `json:"message"`
	Result  struct {
		Address_component struct {
			Address
		} `json:"address_component"`
	} `json:"result"`
}

type translateResp struct {
	Status    int64      `json:"status"`
	Message   string     `json:"message"`
	Locations []Location `josn:"locations"`
}

func NewOption(key, secret string) *option {
	return &option{
		key:    key,
		secret: secret,
	}
}

// 根据地址获取经纬度
func (option *option) GetLocation(adderss, city string) (*Location, error) {
	param := map[string]string{
		"address": adderss,
	}
	if city != "" {
		param["region"] = city
	}

	ret, err := option.request(param, "/ws/geocoder/v1")
	if err != nil {
		return nil, err
	}
	data := locationResp{}
	err = json.Unmarshal(ret, &data)
	if err != nil {
		return nil, err
	}

	if data.Status != 0 {
		return nil, errors.New(data.Message)
	}
	return &data.Result.Location.Location, err
}

// 根据经纬度获取地址
func (option *option) GetAdderss(lat, lng float64) (*Address, error) {
	param := map[string]string{
		"location": fmt.Sprintf("%v,%v", lat, lng),
	}
	ret, err := option.request(param, "/ws/geocoder/v1")

	if err != nil {
		return nil, err
	}

	data := addressResp{}
	err = json.Unmarshal(ret, &data)

	if err != nil {
		return nil, err
	}
	if data.Status != 0 {
		return nil, errors.New(data.Message)
	}
	return &data.Result.Address_component.Address, nil
}

// 根据ip获取经纬度
func (option *option) GetIpLocation(ip string) (*Location, *Address, error) {
	param := map[string]string{
		"ip": ip,
	}
	ret, err := option.request(param, "/ws/location/v1/ip")
	if err != nil {
		return nil, nil, err
	}
	data := IPLocationResp{}
	err = json.Unmarshal(ret, &data)
	if err != nil {
		return nil, nil, err
	}
	if data.Status != 0 {
		return nil, nil, errors.New(data.Message)
	}
	return &data.Result.Location.Location, &data.Result.Address.Address, nil
}

// 将坐标转换为腾讯坐标
func (option *option) Translate(lat, lng float64, sourceType uint) (*Location, error) {
	param := map[string]string{
		"locations": fmt.Sprintf("%v,%v", lat, lng),
		"type":      fmt.Sprint(sourceType),
	}
	ret, err := option.request(param, "/ws/coord/v1/translate")
	if err != nil {
		return nil, err
	}
	data := translateResp{}
	err = json.Unmarshal(ret, &data)
	if err != nil {
		return nil, err
	}
	if data.Status != 0 {
		return nil, errors.New(data.Message)
	}

	if len(data.Locations) > 0 {
		return &data.Locations[0], nil
	}
	return nil, err
}

func (opton *option) request(param map[string]string, url string) ([]byte, error) {
	param["key"] = opton.key
	keys := maputil.Keys(param)
	sort.Strings(keys)
	tmp := []string{}
	for _, k := range keys {
		tmp = append(tmp, k+"="+param[k])
	}

	url = url + "?" + strings.Join(tmp, "&")
	sig := cryptor.Md5String(url + opton.secret)
	url = "https://apis.map.qq.com" + url + "&sig=" + sig
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return ioutil.ReadAll(resp.Body)

}

// 计算两个坐标的距离
func Distance(lat1, lng1, lat2, lng2 float64) float64 {
	radius := 6370.99681
	rad := math.Pi / 180.0
	lat1 = lat1 * rad
	lng1 = lng1 * rad
	lat2 = lat2 * rad
	lng2 = lng2 * rad

	theta := lng2 - lng1
	dist := math.Acos(math.Sin(lat1)*math.Sin(lat2) + math.Cos(lat1)*math.Cos(lat2)*math.Cos(theta))
	return dist * radius
}
